﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Xml;
using System.ComponentModel;
using System.Reflection;
using MediaRecommender.Controller;

//using http://james.newtonking.com/json to read through json
namespace MediaRecommender.Model
{
    class MediaData
    {
        //Api keys required to access data from media sites, isbndb.com and themoviedb.org
        private string TMDb_apiKey = "ffb2c95850522361084f2387925098f8";
        private string ISBNDb_apiKey = "252FGT1E"; //limited to 500requests per day, due to payment restrictions

        private string movieBaseURL = "http://api.themoviedb.org/3/";
        private string baseMovieCoverUrl = "http://d3gtl9l2a4fn1j.cloudfront.net/t/p/original"; //the movie db returns a cover-image id, this is the base url for that
        private string gameBaseURL = "http://thegamesdb.net/api/";
        private string baseGameCoverUrl = "http://thegamesdb.net/banners/";
        private string bookBaseURL = "http://isbndb.com/api/v2/xml/";
        

        //To keep track of total movies actually downloaded and number of expectet movies
        public int TotalMovieCounter { get; private set; }
        private int TotalExpectet = 0;

        
        public MediaData()
        {
            bookBaseURL += ISBNDb_apiKey + "/";
        }

        //Because of problems with our database not accepting (large) enums we have had to save them in an int list instead
        private List<int> convertEnumToInt<T>(List<T> genreIn)
        {
            List<int> toReturn = new List<int>();
        
            foreach (var genre in genreIn)
            {
                toReturn.Add(Convert.ToInt32(genre));
            }

            return toReturn;
        }
        private string getData(string URL)
        {
            //Heavily based on code snippet from; https://www.themoviedb.org/talk/52640d8f19c2951a08042ce6
            var request = (HttpWebRequest)WebRequest.Create(URL);
            request.Method = "GET";
            request.ContentLength = 0;
            string responseContent = "";
            using (var response = request.GetResponse() as HttpWebResponse)
            {
                if (null != response && HttpStatusCode.OK == response.StatusCode) //makes sure the response is valid
                {
                    using (var reader = new StreamReader(response.GetResponseStream()))
                    {
                        responseContent = reader.ReadToEnd();
                    }
            }
                else return null; //Might need to implement a status code appropriate for error
            }

            return responseContent;
        }

        private int getXmlData(string xmlFormattedString)
        {
            //variables used to build book object
            int toReturn = 0;
            string title = "";
            List<Author> authors = new List<Author>();
            List<Genre> genres = new List<Genre>();

            //copies xml string into a xml document format
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xmlFormattedString);

            //splits the document up into nodes, selecting the nodes called "data"
            XmlNodeList nodes = xmlDoc.DocumentElement.SelectNodes("//data");

            AssociatedPeopleController apc = new AssociatedPeopleController();

            //runs through each "data" node
            foreach (XmlNode node in nodes)
            {
                //clears the genre and authors list, so that we can fill new data in them
                genres.Clear();
                authors.Clear();
                //because its not a necessity for a book to have an author
                if (node.SelectSingleNode("author_data") != null)
                {
                    string author = node.SelectSingleNode("author_data").SelectSingleNode("name").InnerText;
                    authors.Add(apc.CreateAssociatedPerson<Author>(author));
                }
                title = node.SelectSingleNode("title").InnerText;
                Genre currGenre;
                XmlNodeList genreNodes = node.SelectNodes("subject_ids");
                foreach (XmlNode genreNode in genreNodes)
                {
                    if (Enum.TryParse<Genre>(genreNode.InnerText.Replace("_", ""), out currGenre))
                        genres.Add(currGenre);
                }

                MediaController mc = new MediaController();

                if(mc.CreateMedia(authors, title, convertEnumToInt<Genre>(genres), ""))
                    toReturn++;
            }
            return toReturn;
        }

        public int getJsonData(string jsonFormattedString)
        {
            //variables needed to build the 
            List<Genre> genres = new List<Genre>();
            string title = "";
            string director = "";
            List<Actor> actors = new List<Actor>();
            string coverImage = "";
            AssociatedPeopleController apc = new AssociatedPeopleController();

            /*To get the data we need from the json object, we do string manipulation. 
             * There are an arguably clearer way of doing it by making throw away classes that matches every attribute in the json object. 
             * But since the json object contains alot of information we dont need this felt faster
             * 
             * 
             */
            dynamic parsedObject = JsonConvert.DeserializeObject(jsonFormattedString); //the dynamic type means the type is defined at runtime.
            foreach (dynamic entry in parsedObject) //runs through each object in the json object
            {
                string name = entry.Name; //gets the name of the current json object
                dynamic value = entry.Value; //gets the information of the current json object
                if (name.Equals("genres"))
                {
                    foreach (dynamic d in value)
                    {
                        string currValue = Convert.ToString(d);
                        string[] strings = currValue.Split('\"');
                        string prev = "";
                        for (int i = 0; i < strings.Length; i++)
                        {
                            if (prev.Equals("name"))
                            {
                                Genre temp2;
                                string temp = strings[i + 1].Replace(" ", "").Replace("-", "").ToLower();
                                if (Enum.TryParse(temp, out temp2))
                                {
                                    genres.Add(temp2);
                                }
                            }
                            prev = strings[i];
                        }
                    }
                }
                else if (name.Equals("original_title")) title = Convert.ToString(value);
                else if (name.Equals("poster_path")) coverImage = baseMovieCoverUrl + Convert.ToString(value);
                else if (name.Equals("credits"))
                {
                    string valueString = Convert.ToString(value);
                    String[] sections = valueString.Split('[', ']');
                    //actors

                    string[] cast = sections[1].Split('{', '}');
                    foreach (string s in cast)
                        if (s.Contains("name"))
                        {
                            string[] actorInfo = s.Split(',');
                            string[] actorNameLine = actorInfo[1].Split(':');
                            actors.Add(apc.CreateAssociatedPerson<Actor>(actorNameLine[1].Replace("\"", "").Trim()));
                        }

                    //director
                    string[] crew = sections[3].Split('{', '}');
                    if (crew.Length > 2)
                    {
                        string[] directorInfo = crew[1].Split(',');
                        string[] directorNameInfo = directorInfo[1].Split(':');
                        director = directorNameInfo[1].Replace("\"", "").Trim();
                    }
                }
            }

            //constructs movie object
            Director newDirector = apc.CreateAssociatedPerson<Director>(director);

            MediaController mc = new MediaController();
            if (mc.CreateMedia(newDirector, actors, title, convertEnumToInt<Genre>(genres), coverImage)) return 1;

            return 0; //returns the number of data added wich if mc.createMedia == false is 0
        }

        #region movies
        public int getMovieById(int id)
        {
            TotalExpectet++;
            string URL = string.Format("{2}movie/{0}?api_key={1}&append_to_response=credits", Convert.ToString(id), TMDb_apiKey, movieBaseURL);

            return getJsonData(getData(URL));
            }

        public int getMovieListByGenreId(int genreId, bool adultFilms, bool includeAll)
        {
            int totalPages = 1;
            int toReturn = 0;
 
            string URLToFindPage = string.Format("{2}genre/{0}/movies?api_key={1}", //at the moment this list does not include; adult films or movies that have been rated ten or less times
                Convert.ToString(genreId),
                TMDb_apiKey,
                movieBaseURL,
                totalPages
                );
            // The returned data is split into pages, 20 movies per page.
            // to find the number of pages, so we can run through them, we read the json obejct.
            dynamic objectToFindPages = JsonConvert.DeserializeObject(getData(URLToFindPage));
            foreach (dynamic entry in objectToFindPages) //runs through the objects in the json object, to find the total pages
            {
                string name = entry.Name;
                dynamic value = entry.Value;
                if (name.Equals("total_pages"))
                    totalPages = Convert.ToInt32(value);
                else if (name.Equals("total_results")) TotalMovieCounter += Convert.ToInt32(value);
        }

            //runs through every page to get all movies
            for (int i = 1; i <= totalPages; i++)
        {
                string URL = string.Format("{2}genre/{0}/movies?api_key={1}&page={3}", //at the moment this list does not include; adult films or movies that have been rated ten or less times
                Convert.ToString(genreId), 
                TMDb_apiKey, 
                    movieBaseURL,
                    i
                );
                WriteToLog.WriteLog("Page number: " + Convert.ToString(i)); 
                dynamic parsedObject = JsonConvert.DeserializeObject(getData(URL));
                foreach (dynamic entry in parsedObject)
                {
                    string name = entry.Name;
                    dynamic value = entry.Value;
                    if (name.Equals("results"))
                    {
                        string valueString = Convert.ToString(value);
                        string[] sections = valueString.Split('{');
                        foreach (string s in sections)
                        {
                            string[] lines = s.Split(',');
                            if (lines.Length > 2)
                            {
                                string[] id = lines[2].Split(':');
                                toReturn += getMovieById(Convert.ToInt32(id[1].Trim()));
                            }
                        }
                    }
                }
            }
            WriteToLog.WriteLog("Total: " + Convert.ToString(TotalMovieCounter) + "/ " + Convert.ToString(TotalExpectet)); 
            return toReturn;
        }

        public int GetAllMovies()
        {
            string URL = string.Format("{1}genre/list?api_key={0}", TMDb_apiKey, movieBaseURL);
            int toReturn = 0;

            dynamic parsedObject = JsonConvert.DeserializeObject(getData(URL));
            foreach (dynamic entry in parsedObject) //runs through every object in the json object
            {

                string name = entry.Name;
                dynamic value = entry.Value;
                string valueString = Convert.ToString(value);
                string[] sections = valueString.Split('{');
                foreach (string s in sections)
                {
                    string[] infoId = s.Split(':');
                    if (infoId.Length > 2)
                    {
                        string[] id = infoId[1].Split(',');
                        toReturn += getMovieListByGenreId(Convert.ToInt32(id[0].Trim()), false, false);
                    }
                }
            }

            return toReturn;
        }
        #endregion

        #region games
        public int getGameByName(string name)
        {
            //Example from here: http://forums.asp.net/t/1881390.aspx?Convert+XML+data+to+string+c+

            //Gets the information from the website as a string.
            string xmlString = getData(gameBaseURL + "GetGame.php?name=" + name);

            //Create an XML Document and loads the xmlString in the XML Document.
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmlString);

            //Get node List of Game nodes.
            XmlNodeList nodes = doc.DocumentElement.SelectNodes("//Game");
            
            string title = null;
            List<Platform> platforms = new List<Platform>();
            string gamestudio = "";
            string publisher = "";
            string coverUrl = "";
            List<Genre> genres = new List<Genre>();

            //Finds information from every node by its specific node.
            foreach (XmlNode node in nodes)
            {
                if (node.SelectSingleNode("GameTitle").InnerText == name)
                {
                    title = node["GameTitle"].InnerText;
                    if (Enum.IsDefined(typeof(Platform), node["Platform"].InnerText.Replace(" ", "")))
                    {
                        platforms.Add((Platform)Enum.Parse(typeof(Platform), node["Platform"].InnerText.Replace(" ", "")));
                        gamestudio = node["Developer"].InnerText;
                        publisher = node["Publisher"].InnerText;
                        coverUrl = node["Images"].FirstChild.InnerText;
                    }
                    if (node.SelectSingleNode("Genres") != null)
                    {
                        XmlNode genreNode = node["Genres"];
                        Genre tempGenre;
                        foreach (XmlNode genre in genreNode.SelectNodes("genre"))
                            if(Enum.TryParse(genre.InnerText.ToLower().Replace("-", ""), out tempGenre)) genres.Add(tempGenre);
                            //genres.Add((Genre)Enum.Parse(typeof(Genre), genre.InnerText.ToLower().Replace("-", "")));
                    }
                }
            }

            //Creating a new VideoGame
            AssociatedPeopleController apc = new AssociatedPeopleController();
            GameStudio newGamestudio = apc.CreateAssociatedPerson<GameStudio>(gamestudio);
            Publisher newPublisher = apc.CreateAssociatedPerson<Publisher>(publisher);

            MediaController mc = new MediaController();
            if(mc.CreateMedia(newGamestudio, newPublisher, convertEnumToInt<Platform>(platforms), title, convertEnumToInt<Genre>(genres), coverUrl)) return 1;

            return 0;
        }

        public int getPlatformList(string platform)
        {
            int id = 0;

            //Create an XML Document and loads the xmlString in the XML Document.
            string xmlString = getData(gameBaseURL + "GetPlatformsList.php");

            //Create an XML Document and loads the xmlString in the XML Document.
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmlString);

            //Get node List of Platform nodes.
            XmlNodeList nodes = doc.DocumentElement.SelectNodes("//Platform");

            //Finding the id for the specific Platform
            foreach (XmlNode node in nodes)
            {
                if (node.SelectSingleNode("name").InnerText == platform)
                {
                    id = Convert.ToInt32(node.SelectSingleNode("id").InnerText);
                }
            }

            return getGamesByPlatformId(id, platform);
        }

        public int getGamesByPlatformId(int id, string platform)
        {  
            string xmlString = getData(gameBaseURL + "GetPlatformGames.php?platform=" + Convert.ToString(id));

            //Create an XML Document and loads the xmlString in the XML Document.
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmlString);

            //Get node List of Game nodes.
            XmlNodeList nodes = doc.DocumentElement.SelectNodes("//Game");

            string gameTitle = null;
            int toReturn = 0; 

            //Finds information about every games.
            foreach (XmlNode node in nodes)
            {
                gameTitle = node.SelectSingleNode("GameTitle").InnerText;
                toReturn += getGameByName(gameTitle, platform);
            }

            return toReturn;
        }

        public int getGameByName(string name, string platform)
        {
            //Example from here: http://forums.asp.net/t/1881390.aspx?Convert+XML+data+to+string+c+

            //Create an XML Document and loads the xmlString in the XML Document.
            string xmlString = getData(gameBaseURL + "GetGame.php?name=" + name);

            //Create an XML Document and loads the xmlString in the XML Document.
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmlString);

            //Get node List of Game nodes.
            XmlNodeList nodes = doc.DocumentElement.SelectNodes("//Game");

            string title = "";
            List<Platform> platforms = new List<Platform>();
            string gamestudio = "";
            string publisher = "";
            string coverUrl = "";
            List<Genre> genreList = new List<Genre>();

            //Finds information from every note by its specific node.
            foreach (XmlNode node in nodes)
            {
                if (node.SelectSingleNode("GameTitle").InnerText == name && node.SelectSingleNode("Platform").InnerText == platform)
                {
                    
                    title = node["GameTitle"].InnerText;
                    //platforms.Add((Platform)Enum.Parse(typeof(Platform), node["Platform"].InnerText.Replace(" ", "")));
                    if (node.SelectSingleNode("Developer") != null)
                        gamestudio = node["Developer"].InnerText;
                    if (node.SelectSingleNode("Publisher") != null)
                        publisher = node["Publisher"].InnerText;
                    if (node.SelectSingleNode("Images") != null)
                    {
                        if (node.SelectSingleNode("Images").SelectSingleNode("boxart") != null)
                            coverUrl = baseGameCoverUrl + node["Images"].SelectSingleNode("boxart").InnerText;
                    }
                    if (node.SelectSingleNode("Genres") != null)
                    {
                        XmlNode genreNode = node["Genres"];

                        foreach (XmlNode genre in genreNode.SelectNodes("genre"))
                        {
                            //genreList.Add((Genre)Enum.Parse(typeof(Genre), genre.InnerText.Replace("-", "").Replace(" ", "")));
                            string testCurr = genre.InnerText.Replace("-", "").Replace(" ", "").ToLower();
                            Genre g;
                            if(Enum.TryParse(testCurr, out g)) genreList.Add(g);

                        }
                    }
                    break;
            }
            }

            //Constructs
            AssociatedPeopleController apc = new AssociatedPeopleController();
            GameStudio newGamestudio = apc.CreateAssociatedPerson<GameStudio>(gamestudio);
            Publisher newPublisher = apc.CreateAssociatedPerson<Publisher>(publisher);

            MediaController mc = new MediaController();
            if(mc.CreateMedia(newGamestudio, newPublisher, convertEnumToInt<Platform>(platforms), title, convertEnumToInt<Genre>(genreList), coverUrl)) return 1;

            return 0;
        }
             
        #endregion

        #region books
        public int GetBookByTitle(string title)
        {
            title = title.Replace(" ", "_");
            string URL = string.Format("{0}book/{1}",
                bookBaseURL,
                title
                );

            return getXmlData(getData(URL));
        }

        public int GetBooksByGenre(string genre)
        {
            int totalPages = 0;

            string baseURL = string.Format("{0}books?q={1}",
                bookBaseURL,
                genre
                );
            string xmlData = getData(baseURL);
            int toReturn = 0;

            XmlDocument xmlDoc = new XmlDocument();

            xmlDoc.LoadXml(xmlData);

            // The returned data is split into pages.
            // to find the number of pages, so we can run through them, we read the page_count xml node.
            XmlNode node = xmlDoc.SelectSingleNode("//page_count");
            totalPages = Convert.ToInt32(node.InnerText);
            WriteToLog.WriteLog(Convert.ToString(totalPages));
            toReturn += getXmlData(xmlData);
            WriteToLog.WriteLog("Got page 1! in books in genre: " + genre);

            for (int i = 2; i <= totalPages; i++)
            {
                string pageNumber = "&page=" + i;
                string URL = baseURL + pageNumber;

                try
                {
                    toReturn += getXmlData(getData(URL));
                    WriteToLog.WriteLog("Got page " + i + "! in books in genre: " + genre); 
                }
                catch (WebException e)
                {
                    WriteToLog.WriteLog("\nThere were an error accessing page " + Convert.ToString(i) +  " during " + genre + " and it was was skipped: " + e.Message);
                }
            }
            return toReturn;
        }
        #endregion
    }    
}
